package snapex.core.security;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.query.LdapQuery;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.query.SearchScope;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.ldap.userdetails.LdapUserDetails;
import org.springframework.util.StringUtils;

import lombok.extern.slf4j.Slf4j;
import snapex.core.SnapexAuthenticationException;
import snapex.core.ldap.SnapexLdapAuthenticator;
import snapex.core.mapper.EmployeeMapper;
import snapex.core.model.Employee;
import snapex.core.model.User;
import snapex.core.security.LdapUserDetailsConfiguration.LdapUserStoreCondition;

@Slf4j
@Configuration
@Conditional(LdapUserStoreCondition.class)
public class LdapUserDetailsConfiguration {

	@Autowired
	LdapContextSource ldapContextSource;
	
	@Value("${snapex.ldap.userSearchBase}")
	String userSearchBase;
	
	@Value("${snapex.ldap.userSearchFilter}")
	String userSearchFilter;
	
	@Value("${snapex.ldap.groupSearchBase}")
	String groupSearchBase;
		
	@Value("${snapex.ldap.trustStorePath}")
	String trustStorePath;
	
	@Value("${snapex.ldap.trustStorePassword}")
	String trustStorePassword;
	
	@Bean 
	public AuthenticationManagerBuilderDelegate authenticationManagerBuilderService(SnapexLdapAuthenticator ldapAuthenticator) {
		return new AuthenticationManagerBuilderDelegate() {
			@Override
			public void build(AuthenticationManagerBuilder auth) throws Exception{
								
				if(!StringUtils.isEmpty(trustStorePath)) {
					Resource keyStoreResource = null;
					if(trustStorePath.startsWith("classpath:")) {
						keyStoreResource = new ClassPathResource(trustStorePath.replaceFirst("classpath:",""));
					}else {
						keyStoreResource = new FileSystemResource(trustStorePath.replaceFirst("file:",""));
					}
					
					if(keyStoreResource.exists()) {
						System.setProperty("javax.net.ssl.trustStore", keyStoreResource.getFile().getCanonicalPath());
						System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
					}
				}
				
				auth.ldapAuthentication()
					.groupSearchBase(groupSearchBase)
					.contextSource(ldapContextSource)
					.userSearchBase(userSearchBase)
					.userSearchFilter(userSearchFilter);
			}	
			@Override
			public User getUserDetails(Object principal){
				
				if(principal instanceof LdapUserDetails) {
					return ldapAuthenticator.getPrincipal(((LdapUserDetails)principal).getUsername());	
				}else {
					return (User)principal;
				}
			}
		};
	}

	@Bean
	public SnapexLdapAuthenticator ldapAuthenticator(
			LdapTemplate ldapTemplate,
			EmployeeMapper mapper, 
			@Value("${snapex.ldap.userSearchBase}") String baseDn,
			@Value("${snapex.ldap.userSearchFilter}") String filterFormat) {

		return new SnapexLdapAuthenticator() {
			
			@Override
			public Employee authentiate(String username, String password) {
//				
//				LdapQuery query = LdapQueryBuilder.query().base(baseDn).searchScope(SearchScope.SUBTREE)
//						.filter(filterFormat, username);
//				Optional<Employee> staff = ldapTemplate.search(query, mapper).stream().findAny();
//
//				if (staff.isPresent()) {
//
//					log.debug("*****Validating credential for user({}), staff id({})*****", username, staff.get().getStaffId());
//					
//					try {
//						ldapContextSource.getContext(staff.get().getDistinguishedName(), password);
//					} catch (AuthenticationException e) {
//						throw new SnapexAuthenticationException("Bad Credentials", e);
//					}
//
//					log.debug("*****Validated credential for for user({}), staff id({})*****", username, staff.get().getStaffId());
//					
//					return staff.get();
//				}
//
//				log.debug("*****[authentiate]User({}) not found in AD*****", username);
//				
//				throw new SnapexAuthenticationException("Bad Credentials: Cound not find user from AD");
				return null;
			}

			@Override
			public Employee getPrincipal(String username) {
				
				log.debug("***Start to get employee detail from LDAP****");
				
				LdapQuery query = LdapQueryBuilder.query().base(baseDn).searchScope(SearchScope.SUBTREE)
						.filter(filterFormat, username);
				Optional<Employee> staff = ldapTemplate.search(query, mapper).stream().findAny();
				
				log.debug("***Complete get employee detail from LDAP****");
				
				if (staff.isPresent()) {
					return staff.get();
				}
				
				throw new SnapexAuthenticationException("User not found");
			}
		};
	}
	
	static class LdapUserStoreCondition extends SpringBootCondition {

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {

			ConditionMessage.Builder message = ConditionMessage.forCondition("snapex.security.userStore Condition");
			
			String userStore = context.getEnvironment().getProperty("snapex.security.userStore");
			
//			log.debug("****userStore:{}", userStore);
			
			if ("LDAP".equalsIgnoreCase(userStore)) {
				return ConditionOutcome.match(message.foundExactly("LDAP userStore"));
			}
			return ConditionOutcome.noMatch(message.didNotFind("LDAP userStore").atAll());
		}
	}
}
